<?php

define('IRCBot_Version', 'Adria ver. 0.2, build:17032007-1');
define('IRCBot_ConfigFile', 'config.php');

require_once('class_event.php');
require_once('class_codeevent.php');
require_once('class_irc.php');
require_once('class_log.php');
require_once('inc/ConfFile.php');
require_once('inc/EventsFile.php');
require_once('inc/userFile.php');
require_once('inc/CronFile.php');
require_once('inc/CommandFile.php');

$Users=new userFile(); // Global instance of userFile (provide manipulation with user cache)
$IRC=new IRC();	 // Global instance of IRC class (provide comunication)
$Log=new Log();	 // Global instance of Log class (provide saving logs)
$Bot=new IRCBot; // Global instance of IRC bot - becouse Bot can be only one
$Cron=new CronFile();
$Commands=new CommandFile();

class IRCBot {

  private $Events = array (); 		// list of events and their properties
  private $RunningEvent = array (); // array instances of runninng Events (runnings = has got some triggers)
  private $Config = array ();

  private $Conf;
  private $Command_conf; //sem se otevre trida pro cteni z confu pro moduly

  public function __construct() {
    //$GLOBALS['Log']->ToFile('logs'.DIRECTORY_SEPARATOR.'raw.txt',Log_TypeRaw);
    //$GLOBALS['Log']->ToOutput(Log_TypeAll);
    $GLOBALS['Log']->ToFile('logs'.DIRECTORY_SEPARATOR.'debug_log.txt', Log_TypeDebug);
    $GLOBALS['Log']->ToFile('logs'.DIRECTORY_SEPARATOR.'all_log.txt', Log_TypeAll);
    //$GLOBALS['Log']->ToSQLite('logs'.DIRECTORY_SEPARATOR.'raw.db',Log_TypeRaw);
    /*$this->Config['server'][0]['port'] = 6667;
     $this->Config['server'][0]['url'] = "irc.smoula.net";
     $this->Config['nick'] = Array ('Adria','Adruska');//,'Ariak','Adroid');
     $this->Config['channel'] = Array ('#sg.cz','#adria');
     $this->Config['user_message'] = Array (
     0=>'ble',
     3=>$this->Config['nick'][0].' 8 * :BLPSBot v1.01 :'.IRCBot_Version,
     1=>'a a a a',
     'default'=>$this->Config['nick'][0].' BLPSBot v1.01 :'.IRCBot_Version);
     $this->Config['timeout']['user_message']=5;
     $this->Config['timeout']['nick_message']=5;
     $this->Config['timeout']['connect']=10;
     //*/
    Set_Time_Limit(0); //  timelimit=0
    Ignore_User_Abort(1); //  user abort ignore
    if(!$this->LoadConfig())
  		{
  		  $GLOBALS['Log']->Sys('Unable to load config file ('.IRCBot_ConfigFile.')');
  		  die();
  		}//*/

    //$this->Start();
  }

  public function GetConfig($section=NULL,$value=NULL)
  {
    return $section!=NULL?($value!=NULL?$this->Config[$section][$value]:$this->Config[$section]):$this->Config;
  }
  /*
   * This function try to connect to IRC server throught IRC object
   */
  public function Start()
  {
    $IRC=$GLOBALS['IRC'];
    $i=0;
    while ((!$IRC->IsConnected()) and isset($this->Config['server'][$i]))
    {
  	   $GLOBALS['Log']->Sys('---------------------------------------------------------------------------');
  	   $GLOBALS['Log']->Sys('Trying connect with '.$this->Config['server'][$i]['url'].':'.$this->Config['server'][$i]['port']."\n");
  	   if (!$IRC->Connect($this->Config['server'][$i]['url'],$this->Config['server'][$i]['port'],$ErrNo,$ErrStr,$this->Config['timeout']['connect']))
  	   {
  	     $GLOBALS['Log']->Sys('!! Connect with IRC server fails !!');
  	     $GLOBALS['Log']->Sys('Server: '.$this->Config['server'][$i]['url'].':'.$this->Config['server'][$i]['port'].', timeout: '.$this->Config['timeout']['connect']);
  	     $GLOBALS['Log']->Sys('Error: '.$ErrNo.' - '.$ErrStr);
  	   }
  	   $i++;
    }
    if (!$IRC->IsConnected())
    {
      $GLOBALS['Log']->Sys('All connections ('.$i.') fails. Please try to add some servers in your IRC network, or check your server settings.');
      return false;
    }
    else
    {
      // Now we have got a opened socket with IRC server
      $GLOBALS['Log']->Sys('Connected ...');
      // USER Message -----------------------
      $GLOBALS['Log']->Sys('Sending USER Message ...');
      $IRC->ScWrite('USER '.$this->Config['user_message'][0],false);
      $regexps_user_fails=array(
      ':[^ ]+ '.ERR_NEEDMOREPARAMS.'( [^ ]*)? USER .*'
       );
      $code_user_fails = "	\$GLOBALS['Log']->Sys(\"User message error\nTrying other message...\");
					\$this->TTL = (Time() + 5);
					\$this->Data++;
					\$Config=\$GLOBALS['Bot']->GetConfig();
					if (isset(\$Config['user_message'][\$this->Data]))
					   {\$user_message=\$Config['user_message'][\$this->Data];}
					else
                       {\$user_message=\$Config['user_message']['default'];} 
					\$GLOBALS['IRC']->ScWrite('USER '.\$user_message,false);
					\$GLOBALS['Log']->Sys('Sending NICK Message ...');
      				\$GLOBALS['IRC']->ScWrite('NICK '.\$Config['nick'][0],false);	
					";
      $regexps_user_ok=array(':[^ ]+ '.RPL_WELCOME.'.*',
      ':[^ ]+ '.ERR_ALREADYREGISTRED.'.*');	// becouse of if server send welcome message we muss be logged in
      $code_user_ok="\$Config=\$GLOBALS['Bot']->GetConfig();
					   if (!isset(\$Config['user_message'][\$this->Data]))
					      {\$user_message=\$Config['user_message'][\$this->Data];}
					   else
                          {\$user_message=\$Config['user_message']['default'];} 
					  \$GLOBALS['Log']->Sys('Used USER message : '.\$user_message);
					  \$this->DestroyAllTriggers(); // free nick messages
					";
       
      // USER message event
      $tmp=new CodeEvent($this->Config['timeout']['user_message']);
      $tmp->SetData(0);
      $tmp->AddTrigger($regexps_user_ok,$code_user_ok);
      $tmp->AddTrigger($regexps_user_fails,$code_user_fails);
      $this->NewRunningEvent($tmp);
      unset($tmp);
       
      // NICK Message -----------------------------------------------------
      $GLOBALS['Log']->Sys('Sending NICK Message ...'."\n");
      $IRC->ScWrite('NICK '.$this->Config['nick'][0],false);
      $IRC->SetAttrib('Nick',$this->Config['nick'][0]);
      $regexps_nick_fails=array(
						':[^ ]+ '.ERR_NONICKNAMEGIVEN.'.*',
						':[^ ]+ '.ERR_ERRONEUSNICKNAME.'( [^ ]*)? '.$this->Config['nick'][0].'.*',
						':[^ ]+ '.ERR_NICKCOLLISION.'( [^ ]*)? '.$this->Config['nick'][0].'.*',
						':[^ ]+ '.ERR_UNAVAILRESOURCE.'( [^ ]*)? '.$this->Config['nick'][0].'.*',
						':[^ ]+ '.ERR_RESTRICTED.'.*',
						':[^ ]+ '.ERR_NICKNAMEINUSE.'( [^ ]*)? '.$this->Config['nick'][0].'.*'
					   );

						$code_nick_fails="
				   \$GLOBALS['Log']->Sys(\"Nick error\nTrying other nick...\");
				   \$this->TTL = (Time() + 5);
				   \$this->Data++;
				   \$Config=\$GLOBALS['Bot']->GetConfig();
				   if (!isset(\$Config['nick'][\$this->Data])) 
						{\$Nick=\$Config['nick'][0].strval(rand(0,1000));}
				   else \$Nick=\$Config['nick'][\$this->Data];
				   // change regexps 
				   foreach(\$this->Trigger as &\$Exp)
					 {\$Exp['RegExp']=str_replace(\$GLOBALS['IRC']->GetNick(),\$Nick,\$Exp['RegExp']);}
				   \$GLOBALS['IRC']->SetAttrib('Nick',\$Nick);
				   \$GLOBALS['IRC']->ScWrite('NICK '.\$Nick,false);
			  	 ";
							
						$regexp_nick_ok=':[^ ]+ '.RPL_WELCOME.'.*';
						$code_nick_ok="\$GLOBALS['Log']->Sys('Used Nick is '.\$GLOBALS['IRC']->GetNick());
					 \$this->DestroyAllTriggers(); // free nick messages
					//prepare this server hosmask
					eregi(':([^ ]+).*',\$GLOBALS['IRC']->GetLastLine(),\$tmp);
					\$GLOBALS['IRC']->SetAttrib('Server',\$tmp[1]);
					\$GLOBALS['Log']->Sys('Get server\'s hostname: '.\$tmp[1]);
					// join to all channels form list
					\$Config=\$GLOBALS['Bot']->GetConfig();
					foreach ((\$Config['channel']) as \$value)
		 			{
		   				\$GLOBALS['Log']->Sys('Trying join to '.\$value.'... ');
						\$GLOBALS['IRC']->Join(\$value);
		 			}
					";
							
						// NICK message Event
						$timeout=($this->Config['timeout']['user_message']*count($this->Config['user_message']))+$this->Config['timeout']['nick_message'];
						$tmp=new CodeEvent($timeout);
						$tmp->SetData(0);
						$tmp->AddTrigger($regexps_nick_fails,$code_nick_fails);
						$tmp->AddTrigger($regexp_nick_ok,$code_nick_ok);
						$this->NewRunningEvent($tmp);
						unset($tmp);
						//------------------------------------------------------------
						$this->LoadEvents();
    }
    unset($IRC);
  }

  // smycka, ktera celou dobu pobezi a bude cist ze socketu
  public function Engine() {
    while (!$GLOBALS['IRC']->EOF()) {
      $IRCLine = $GLOBALS['IRC']->ScReadLine();
      if ($IRCLine != '') $this->ProcessLine($IRCLine);
      else $GLOBALS['IRC']->ScWrite('',false);
      $this->Timers();
    }
    unset($GLOBALS['IRC']);
    $GLOBALS['IRC']=new IRC;
  }

  // pokud prijde nejaky radek, tak metoda ProcessLine porovna regexpy vsech eventu a pokud se shoduji, tak je pusti
  private function ProcessLine($IRCLine) {
    if (count($this->RunningEvent) > 0)
    {
      end($this->RunningEvent);
      do {
        $EID = Key($this->RunningEvent);
        $tmp = $this->RunningEvent[$EID]->ResumeEvent($IRCLine);
        // if There are no triggers, we can delete instace
        if (isset($this->RunningEvent[$EID]) && ($this->RunningEvent[$EID]->TriggerCount() < 1)) unset ($this->RunningEvent[$EID]);
        $Continue = Prev($this->RunningEvent);
      } while ($Continue != FALSE);
    }

    // Controls RegExps of all events defined in events.php
    foreach ($this->Events as &$EventName )
    {
      // projde vsechny eventy
      $RegExp = false;
      if (isset($EventName['regexp'])) foreach($EventName['regexp'] as &$EventRegExp )
      {
        // projde vsechny regexpy prirazene u jednoho eventu
        if (ereg($EventRegExp, $IRCLine))
        {
          $RegExp = true;
          break;				// to uz pak muzeme breaknout, ne ?
        }
      }
      if ($RegExp)
      {
        // zde se pousti event
        //$EID = $this->GetNewEID();
        $tmp = new Event($EventName['file'][0]);
        if (!$tmp->Execute('n/a',$Err))		// If FALSE, Error is Defined in $Err
        {
      	   // Delete Event if error
      	   unset ($tmp);
        }
        elseif ($tmp->TriggerCount() < 1) unset ($tmp);
        else {
          $EID = $this->GetNewEID();
          $this->RunningEvent[$EID] = $tmp;
          unset($tmp);
        }
      }
    }
    unset($tmp);
    unset($EID);
    unset($RegExp);
    unset($Continue);
  }

  // this method will be called every second
  private function Timers()
  {
    static $StartTime = false;
    if ($StartTime == false ) $StartTime = time();
    $Events = $GLOBALS['Cron']->Timer($StartTime);
    $StartTime = (time() + 1);
    // CRON
    if (is_array($Events)) {
      foreach($Events as $Event)
      {
        // zde se pousti event
        $tmp = new Event($Event['file']);
        $tmp->SetData($Event['data']);
        $tmp->SetCronTime($Event['time']);
        $Part = 'n/a';
        if (isset($Event['part'])) $Part = $Event['part'];
        if (!$tmp->Execute($Part,$Err))		// If FALSE, Error is Defined in $Err
        {
          // Delete Event if error
          unset ($tmp);
        } elseif ($tmp->TriggerCount() < 1) {
          // if There are no triggers, we can delete instace
          unset ($tmp);
        }
        else {
          $EID = $this->GetNewEID();
          $this->RunningEvent[$EID] = $tmp;
          unset($tmp);
        }
      }

    }
    // Running events timeouts
    if (count($this->RunningEvent) > 0)
    {
      end($this->RunningEvent);

      do {
        $EID = Key($this->RunningEvent);
        $this->RunningEvent[$EID]->Timer($StartTime);
        // if There are no triggers, we can delete instace
        if (isset($this->RunningEvent[$EID]) && ($this->RunningEvent[$EID]->GetTTL() < $StartTime)) unset ($this->RunningEvent[$EID]);
        if (isset($this->RunningEvent[$EID]) && ($this->RunningEvent[$EID]->TriggerCount() < 1)) unset ($this->RunningEvent[$EID]);
        $Continue = Prev($this->RunningEvent);
      } while ($Continue != FALSE);
    }
    unset($tmp);
    unset($Continue);
    unset($EID);
    $Events = array();
    unset($Events);
  }
  private function GetNewEID()
  {
    static $MaxEID=1;
    if (count($this->RunningEvent) < 1) $MaxEID = 1;
    while (isset ($this->RunningEvent[$MaxEID])) $MaxEID++;
    return $MaxEID;
  }

  public function NewRunningEvent($Event)
  {
    if((get_class($Event)!='Event') or (is_subclass_of($Event,'Event')))
    {
      $this->RunningEvent[$this->GetNewEID()] = $Event;
      return true;
    }
    else return FALSE;
  }
  // Loads the config file
  public function LoadConfig()
  {
    $this->Config = Array ();
    $cf = new ConfFile();
    if ($cf->Open(IRCBot_ConfigFile)!=False)
    {
      // file loaded
      $tmp=$cf->Load();
      unset($cf);
      if (isset($tmp['user-defined'])) $this->Config['user-defined']=$tmp['user-defined'];
      $i=0;
      foreach (($tmp['connect']['server']) as $val)
      {
        list($this->Config['server'][$i]['url'],$this->Config['server'][$i]['port'])=explode(':',$val);
        $i++;
      }
      $this->Config['nick']=$tmp['connect']['nick'];
      $this->Config['channel']=$tmp['connect']['channel'];
      $this->Config['user_message']=$tmp['connect']['user_message'];
      $this->Config['user_message']['default']=$this->Config['nick'][0].' BLPSBot v1.01 :'.IRCBot_Version;
      if (isset($tmp['connect']['user_message_timeout'][0]) and ($tmp['connect']['user_message_timeout'][0]>0))
      $this->Config['timeout']['user_message']=$tmp['connect']['user_message_timeout'][0];
      else $this->Config['timeout']['user_message'][0]=5;
      if (isset($tmp['connect']['nick_message_timeout'][0]) and ($tmp['connect']['nick_message_timeout'][0]>0))
      $this->Config['timeout']['nick_message']=$tmp['connect']['nick_message_timeout'][0];
      else $this->Config['timeout']['nick_message']=5;
      if (isset($tmp['connect']['connect_timeout'][0]) and ($tmp['connect']['connect_timeout'][0]>0))
      $this->Config['timeout']['connect']=$tmp['connect']['connect_timeout'][0];
      else $this->Config['timeout']['connect']=10;
      $this->Config['Events']['cache_file']=(isset($tmp['events']['cache_file'][0])?(str_replace(array('\\','/'),array(DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR),$tmp['events']['cache_file'][0])):'events.php');
      $this->Config['Events']['events_dir']=(isset($tmp['events']['events_dir'][0])?(str_replace(array('\\','/'),array(DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR),$tmp['events']['events_dir'][0])):'events'.DIRECTORY_SEPARATOR);
      $this->Config['Events']['default_timeout']=(isset($tmp['events']['default_timeout'][0])?($tmp['events']['default_timeout'][0]):180);
      $this->Config['Users']['user_file']=str_replace(array('\\','/'),array(DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR),$tmp['users']['user_file'][0]);
      // try load user file
      if (file_exists($this->Config['Users']['user_file']))
      {
        if($GLOBALS['Users']->Open($this->Config['Users']['user_file']))
        {
          $GLOBALS['Users']->Load();
          $GLOBALS['Log']->Sys('User cache ('.$this->Config['Users']['user_file'].') sucefull loaded..');
        }
        else $GLOBALS['Log']->Sys('User cache ('.$this->Config['Users']['user_file'].') loadeding error..');
      }
      else $GLOBALS['Log']->Sys('Can\'t load user cache in '.$this->Config['Users']['user_file'].' - file not exists !');
      return true;
    }
    else return false;
  }
  //Loads Event file
  public function LoadEvents() {
    $this->Events = Array ();
    $Events_Conf = new EventsFile('events.php'); //otevre to conf na eventy
    $Events_Conf->Load();
    $this->Events = $Events_Conf->GetData();
    unset($Events_Conf);
    foreach($this->Events as &$Event)
    {
      $Event['file'][0]=str_replace(array('/','\\'),array(DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR),$Event['file'][0]);
    }
  }

  /**
   * Checks if a regexp exists in triggers
   *
   * @param string $RegExp
   * @return bool
   */
  public function InTriggers($RegExp)
  {
    foreach ($this->RunningEvent as $Event){
      if ($Event->InTriggers($RegExp)) return true;
    }
    return false;
  }

  public function __destruct() {
    if($this->Socket) fclose($this->Socket);
  }
}


//$Bot = new IRCBot;
$Bot->Start();
$Bot->Engine();
?>